package be.ipl.finito.utilitaires;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;

import javax.servlet.ServletContext;

import be.ipl.finito.domaine.Joueur;
import be.ipl.finito.domaine.Partie;
import be.ipl.finito.exception.PartieException;
import be.ipl.finito.servlets.Attente;
import be.ipl.finito.usecases.GestionPartie;
import be.ipl.finito.util.Util;

public class GestionAttente
{

	private static Logger LOGGER = Logger.getLogger("Gestion Attente");

	public static String PAGE_ATTTENTE = "attente.html";
	public static String PAGE_JEU = "servletJeu.html"; // TODO servletJeu.html
	public static String PAGE_ERREUR = "erreur.html";
	public static String PAGE_MENU = "menu.html";

	private List<Joueur> joueurChecker;
	private Timer timer;
	private Partie partie;
	private Etat etat;
	private Attente servlet;
	private String destination;

	public GestionAttente(final Partie partie, final Joueur joueur, final Attente servlet)
	{
		this.servlet = servlet;
		this.partie = partie;
		this.joueurChecker = new ArrayList<>();
		this.etat = Etat.EN_ATTENTE;
		this.destination = PAGE_ATTTENTE;
		createTimer();
	}

	public boolean checkerJoueur(final Joueur joueur)
	{
		Util.checkObject(joueur);
		return !this.joueurChecker.contains(joueur);
	}

	public void createTimer()
	{
		this.timer = new Timer();
	}

	public void stopTimer()
	{
		if (this.timer != null) {
			this.timer.cancel();
			this.timer = null;
		}
	}

	/**
	 * Demarre l'alarme
	 */
	public void start()
	{
		etat.start(this);
	}

	public void lancerPartie() throws PartieException
	{
		etat.lancerPartie(this);
	}

	public void ajouterJoueur(final Joueur joueur)
	{
		etat.ajouterJoueur(this, joueur);
	}

	public void enleverJoueur(final Joueur joueur)
	{
		etat.enleverJoueur(this, joueur);
	}

	public List<Joueur> getJoueurChecker()
	{
		return this.joueurChecker;
	}

	public Timer getTimer()
	{
		return this.timer;
	}

	public void pret() throws PartieException
	{
		etat.pret(this);
	}

	public String getDestination()
	{
		return destination;
	}

	public void supprimerPartieDuContext()
	{
		final ServletContext context = servlet.getServletContext();
		final Map<Partie, GestionAttente> gestionnaire = (Map<Partie, GestionAttente>) context.getAttribute("gestionnairePartie");
		gestionnaire.remove(partie);

		synchronized (context) {
			context.setAttribute("gestionnairePartie", gestionnaire);
		}
	}

	private enum Etat
	{
		EN_ATTENTE
		{
			@Override
			public void start(final GestionAttente gestionnaireAttente)
			{
				// Si le timer n'est pas lanc� on le lance;
				gestionnaireAttente.timer.schedule(gestionnaireAttente.new RemindTask(), Partie.TEMPS_DEBUT_PARTIE * 1000);
				LOGGER.info("Lancement Timer pour " + gestionnaireAttente.partie + " : " + gestionnaireAttente.timer);
			}

			@Override
			public void pret(final GestionAttente timer)
			{
				try {
					lancerPartie(timer);
				} catch (final PartieException e) {
					e.printStackTrace();
					throw new InternalError("impossible de lancer la partie");
				}
				LOGGER.info("Partie : " + timer.partie.getId() + " Pret. timer :" + timer.timer);
			}

			@Override
			public void ajouterJoueur(final GestionAttente timer, final Joueur joueur)
			{
				if (timer.checkerJoueur(joueur)) {
					if (timer.partie.estEnAttente()) {
						timer.joueurChecker.add(joueur);
						LOGGER.info("Ajout de idJoueur :" + joueur.getId() + " au gestionAttente : " + this);

						if (timer.joueurChecker.size() == Partie.MIN_JOUEURS) {
							timer.etat.start(timer);
						} else if (timer.joueurChecker.size() == Partie.MAX_JOUEURS) {
							timer.etat.pret(timer);
						}
					} else if (timer.partie.estEnCours()) {
						if (timer.partie.contientJoueur(joueur.getId())) {
							timer.joueurChecker.add(joueur);
							LOGGER.info("Ajout de idJoueur :" + joueur.getId() + " au gestionAttente : " + this);
						}

						if (timer.partie.getJoueurs().size() == timer.joueurChecker.size()) {
							timer.etat.pret(timer);
						}
					}
				} else {
					LOGGER.info("Deja checker dans la partie " + timer.partie.getId() + ", joueur =" + joueur.getId());
				}
			}

			@Override
			public void enleverJoueur(final GestionAttente timer, final Joueur joueur)
			{
				if (timer.joueurChecker.contains(joueur)) {
					timer.joueurChecker.remove(joueur);
				}

				if (timer.joueurChecker.size() == 0) {
					timer.etat = Etat.ANNULEE;
					timer.supprimerPartieDuContext();
				} else if (timer.joueurChecker.size() < Partie.MIN_JOUEURS) {
					timer.stopTimer();
				}
			}

			@Override
			public void lancerPartie(final GestionAttente timer) throws PartieException
			{
				// Condition rempli pour d�marrer la partie
				if ((timer.partie.estEnCours() && timer.joueurChecker.size() == timer.partie.getJoueurs().size()) || (timer.partie.estEnAttente() && timer.joueurChecker.size() >= Partie.MIN_JOUEURS)) {
					final ServletContext context = timer.servlet.getServletContext();

					final Partie partie = timer.partie;

					final ListeParties partiesEnAttente = (ListeParties) context.getAttribute("partiesEnAttente");
					final ListeParties partiesActives = (ListeParties) context.getAttribute("partiesActives");

					if (partiesEnAttente.containsKey(partie)) {
						final List<Joueur> joueurs = partiesEnAttente.get(partie);

						// Enregistre une nouvelle partie si necessaire
						Partie newPartie = null;
						final GestionPartie gestionPartie = (GestionPartie) context.getAttribute("gestionPartie");

						if (partie.estEnAttente())
							newPartie = gestionPartie.demarrer(partie, joueurs);
						else if (partie.estEnCours())
							newPartie = gestionPartie.reprendre(partie, joueurs);

						// Partie plus en attente
						partiesEnAttente.remove(partie);
						// Partie Active
						partiesActives.put(partie, joueurs);

						LOGGER.info("Partie : " + partie.getId() + " commencer");

						// Si la partie a �t� persister il faut lier les partie (ancienne id -> nouvelle id)
						if (newPartie != null && !partie.equals(newPartie)) {
							LOGGER.info("Nouvelle partie ils faut la lier " + partie.getId() + "->" + newPartie.getId());
							partiesActives.lier(partie, newPartie);

							synchronized (context) {
								context.setAttribute("partiesEnAttente", partiesEnAttente);
								context.setAttribute("partiesActives", partiesActives);
							}

							timer.supprimerPartieDuContext();
							timer.stopTimer();
							timer.etat = Etat.FINI;
							timer.destination = GestionAttente.PAGE_JEU;
							LOGGER.info("******** la partie commence ********");
						}
					} else {
						throw new InternalError("La partie ne fait pas partie des parties en attentes");
					}
				} else {
					LOGGER.info("Fin du decompte et rien se passe id " + timer.partie.getId());
				}
			}
		},
		FINI
		{
			@Override
			public void ajouterJoueur(final GestionAttente timer, final Joueur joueur)
			{
				if (timer.joueurChecker.contains(joueur)) {
					timer.joueurChecker.remove(joueur);
				} else {
					timer.destination = GestionAttente.PAGE_MENU;
				}
			}

			@Override
			public void pret(final GestionAttente gestionAttente)
			{
				LOGGER.info("la partie " + gestionAttente.partie.getId() + " est deja pret");
			}
		},
		ANNULEE
		{
			@Override
			public void ajouterJoueur(final GestionAttente timer, final Joueur joueur)
			{
				timer.destination = GestionAttente.PAGE_MENU;
			}

			@Override
			public void enleverJoueur(final GestionAttente timer, final Joueur joueur)
			{
				return;
			}
		};

		public void ajouterJoueur(final GestionAttente timer, final Joueur joueur)
		{
			throw new InternalError("Impossible d'ajouter le joueur");
		}

		public void enleverJoueur(final GestionAttente timer, final Joueur joueur)
		{
			throw new InternalError("Impossible de quitter la partie");
		}

		public void start(final GestionAttente timer)
		{
			throw new InternalError("Impossible de lancer un decompte");
		}

		public void pret(final GestionAttente gestionAttente)
		{
			throw new InternalError("Impossible de lancer la partie(pret)");
		}

		public void lancerPartie(final GestionAttente gestionAttente) throws PartieException
		{
			throw new InternalError("Impossible de lancer la partie");
		}
	}

	// Lance la partie quand le timer est ecoule
	class RemindTask extends TimerTask
	{
		@Override
		public void run()
		{
			LOGGER.info(" Timer fini pour Partie :" + partie.getNom() + ", id : " + partie.getId() + " avec " + joueurChecker.size() + " joueur en attente");
			try {
				LOGGER.info("*******************************************************************");
				lancerPartie();
			} catch (final PartieException e) {
				throw new InternalError("Impossible de lancer la partie " + e.getMessage());
			}
		}
	}

	@Override
	public String toString()
	{
		return "GestionAttente [joueurChecker=" + joueurChecker + ", partie=" + partie + ", etat=" + etat + "]";
	}

}
